﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace WebPALTT.Lib
{
    /// <summary>
    /// This class contains a collection of ActionItem objects.
    /// The class inherits from IList, ICollection and IEnumerable.
    /// </summary>
    [Serializable()]
    public class ActionItemCollection
        : IList<ActionItem>, ICollection<ActionItem>, IEnumerable<ActionItem>, ISerializable
    {
        public IEnumerator<ActionItem> GetEnumerator() { return mActions.GetEnumerator(); }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return mActions.GetEnumerator(); }

        public ActionItemCollection(SerializationInfo info, StreamingContext ctxt)
        {
            mActions = (List<ActionItem>)info.GetValue("Actions", typeof(List<ActionItem>));
            try
            { mIsReadOnly = info.GetBoolean("IsReadOnly"); }
            catch (SerializationException) { mIsReadOnly = false; }
        }

        public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
        {
            info.AddValue("Actions", mActions);
            info.AddValue("IsReadOnly", mIsReadOnly);
        }

        List<ActionItem> mActions = new List<ActionItem> { };

        bool mIsReadOnly = false;

        public ActionItemCollection() { }

        public int Count { get { return mActions.Count; } }
        /// <summary>
        /// Counts the number of actions in the collection. If includeChildren is true, the children of each action is also counted.
        /// </summary>
        /// <param name="includeChildren"></param>
        /// <returns></returns>
        public int ActionCount(bool includeChildren = false)
        {
            int i = 0;
            foreach (ActionItem ai in this.mActions)
            {
                i++;
                if (includeChildren && ai.HasChildren)
                    i += ai.Children.ActionCount(true);
            }
            return i;
        }

        public bool IsReadOnly { get { return mIsReadOnly; } set { mIsReadOnly = value; } }
        /// <summary>
        /// Finds action by index.
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public ActionItem this[int index] { get { return mActions[index]; } 
            set 
            {
                if (IsReadOnly)
                    throw new NotSupportedException("The collection is read-only.");
                mActions[index] = value; } }
        /// <summary>
        /// Finds action by name
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public ActionItem this[string name]
        {
            get
            {
                int index = IndexOf(name);
                if (index >= 0)
                    return mActions[index];
                return null;
            }
            set
            {
                if (IsReadOnly)
                    throw new NotSupportedException("The collection is read-only.");
                int index = IndexOf(name);
                if (index >= 0)
                    mActions[index] = value;
                else
                    throw new IndexOutOfRangeException("Index with name [" + name + "] was not found and could not be assigned to.");
            }
        }
        /// <summary>
        /// Finds action by key.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public ActionItem this[Guid key]
        {
            get
            {
                int index = IndexOf(key);
                if (index >= 0)
                    return mActions[index];                
                return null;
            }
            set
            {
                if (IsReadOnly)
                    throw new NotSupportedException("The collection is read-only.");
                int index = IndexOf(key);
                if (index >= 0)
                    mActions[index] = value;
                else
                    throw new IndexOutOfRangeException("Index with key {" + key.ToString() + "} was not found and could not be assigned to.");
            }
        }
        /// <summary>
        /// returns an array of ActionItems in the collection. If includeChildren is true, the children of each action is also recursively added.
        /// </summary>
        /// <param name="includeChildren"></param>
        /// <returns></returns>
        public ActionItem[] ToArray(bool includeChildren = false)
        {
            if (includeChildren)
            {
                List<ActionItem> list = new List<ActionItem> { };
                foreach (ActionItem ai in mActions)
                {
                    list.Add(ai);
                    list.AddRange(ai.Children.ToArray(true));
                }
                return list.ToArray();
            }
            else
                return mActions.ToArray();
        }

        public void Add(ActionItem item) 
        {
            if (IsReadOnly)
                throw new NotSupportedException("The collection is read-only.");
            mActions.Add(item);
            RefreshIndexes();
        }

        public void AddRange(IEnumerable<ActionItem> collection) 
        {
            if (IsReadOnly)
                throw new NotSupportedException("The collection is read-only.");
            mActions.AddRange(collection);
            RefreshIndexes();
        }

        public void Clear() 
        {
            if (IsReadOnly)
                throw new NotSupportedException("The collection is read-only.");
            mActions.Clear(); 
        }
        /// <summary>
        /// Return true if the item exists in the collection.
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Contains(ActionItem item) { return mActions.Contains(item); }
        /// <summary>
        /// Returns true if an item in the collection has the same key. If includeChildren is true, the child nodes of items are also searched.
        /// </summary>
        /// <param name="key"></param>
        /// <param name="includeChildren"></param>
        /// <returns></returns>
        public bool Contains(Guid key, bool includeChildren = false)
        {
            foreach (ActionItem item in mActions)
            {
                if (Guid.Equals(item.Key, key))
                    return true;
                if (includeChildren && item.HasChildren)
                    if (item.Children.Contains(key, true))
                        return true;
            }
            return false;
        }

        public bool Contains(ActionItem value, IEqualityComparer<ActionItem> comparer) { return mActions.Contains(value, comparer); }

        public void CopyTo(ActionItem[] array, int arrayIndex) { mActions.CopyTo(array, arrayIndex); }
        /// <summary>
        /// Returns an array of items matching the predicate from the collection. If includeChildren is true, the child nodes of items are also searched.
        /// </summary>
        /// <param name="match"></param>
        /// <param name="includeChildren"></param>
        /// <returns></returns>
        public ActionItem[] FindAll(Predicate<ActionItem> match, bool includeChildren = false)
        {
            List<ActionItem> list = new List<ActionItem> { };
            foreach (ActionItem ai in mActions)
            {
                if (match(ai))
                    list.Add(ai);
                if (includeChildren && ai.HasChildren)
                    list.AddRange(ai.Children.FindAll(match, true));
            }
            return list.ToArray();
        }
        /// <summary>
        /// Returns an first item matching the predicate from the collection. If includeChildren is true, the child nodes of items are also searched.
        /// </summary>
        /// <param name="match"></param>
        /// <param name="includeChildren"></param>
        /// <returns></returns>
        public ActionItem Find(Predicate<ActionItem> match, bool includeChildren = false)
        {
            foreach (ActionItem ai in mActions)
            {
                if (match(ai))
                    return ai;
                if (includeChildren && ai.HasChildren)
                {
                    ActionItem res = ai.Children.Find(match, true);
                    if (res != null)
                        return res;
                }
            }
            return null;
        }
        /// <summary>
        /// Find index by value.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public int IndexOf(ActionItem value) { return mActions.IndexOf(value); }
        /// <summary>
        /// Find Index by name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public int IndexOf(string name)
        {
            for (int i = 0; i < mActions.Count; i++)
                if ( mActions[i].Name.Equals(name)) { return i; }
            return -1;
        }
        /// <summary>
        /// Find Index by key.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public int IndexOf(Guid key)
        {
            for (int i = 0; i < mActions.Count; i++)
                if (Guid.Equals(mActions[i].Key, key)) { return i; }
            return -1;
        }

        public void Insert(int index, ActionItem value) 
        {
            if (IsReadOnly)
                throw new NotSupportedException("The collection is read-only.");
            mActions.Insert(index, value);
            RefreshIndexes();
        }
        /// <summary>
        /// Remove by value.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public bool Remove(ActionItem value) 
        {
            if (IsReadOnly)
                throw new NotSupportedException("The collection is read-only.");
            bool removed = mActions.Remove(value);
            RefreshIndexes();
            return removed;
        }
        /// <summary>
        /// Remove by name
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public bool Remove(string name)
        {
            if (IsReadOnly)
                throw new NotSupportedException("The collection is read-only.");
            int index = IndexOf(name);
            if (index >= 0)
            {
                mActions.RemoveAt(index);
                RefreshIndexes();
                return true;
            }
            return false;
        }
        /// <summary>
        /// Remove by key.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public bool Remove(Guid key)
        {
            if (IsReadOnly)
                throw new NotSupportedException("The collection is read-only.");
            int index = IndexOf(key);
            if (index >= 0)
            {
                mActions.RemoveAt(index);
                RefreshIndexes();
                return true;
            }
            return false;
        }
        /// <summary>
        /// Remove by index.
        /// </summary>
        /// <param name="index"></param>
        public void RemoveAt(int index) 
        {
            if (IsReadOnly)
                throw new NotSupportedException("The collection is read-only.");
            mActions.RemoveAt(index);
            RefreshIndexes();
        }

        /// <summary>
        /// Recursively Corrects the ActionItem.Parent and ActionItem.Index fields.
        /// Leaves the current ActionItem.Parent of Actions directly in this collection if setParent is flase (default).
        /// </summary>
        public void RefreshIndexes(bool setParent = false, ActionItem parent = null)
        {
            for (int i = 0; i < mActions.Count; i++)
            {
                if (!setParent)
                    mActions[i].SetIndex(i);
                else
                    mActions[i].SetParent(parent, i);
            }
        }
    }
}
